//
//  BleManager.swift
//  EPHome
//
//  Created by liujianlong on 2022/8/2.
//

import UIKit
import CoreBluetooth
let BLE = CCBleManager.share

@objc enum BLEStatus : Int
{
    case normal = 0
    case disconnect = 1
    case connecting = 2
    case connectedSucces = 3
    case connectedFail = 4
    case reConnecting = 5
    case DFU = 6
}

///蓝牙状态的通知
public let BLEStatusUpdateNotifyName = "BLEStatusUpdateNotifyName"
private let lastConnectedUUIDKey = "lastConnectedUUIDKey"
typealias scanResult = ((_ array:[CBPeripheral])->Void)
typealias connectResult = ((_ status:BLEStatus)->Void)
///其他筛选条件
typealias scanFilter = ((_ peripheral:CBPeripheral )->Bool)

//MARK: - 蓝牙中心管理
@objcMembers class CCBleManager : NSObject {
    static let share = CCBleManager()
    private override init() {}
    /// 扫描到的外围设备
    private(set) var searchedPeripherals:[CBPeripheral] = [CBPeripheral](){
        didSet{
            updateScanBlock?(searchedPeripherals)
        }
    }
    /// 连接成功的外围设备
    private(set) var connectedPeripherals:[CBPeripheral] = [CBPeripheral](){
        didSet{
            updateScanBlock?(searchedPeripherals)
            connectBlock?(status)
        }
    }
    ///app上一次启动连接过外设的uuid
    private var lastConnectedUUID = UserDefaults.standard.value(forKey: lastConnectedUUIDKey) as? String ?? ""{
        didSet{
            UserDefaults.standard.set(lastConnectedUUID, forKey: lastConnectedUUIDKey)
            UserDefaults.standard.synchronize()
        }
    }
    private var timer = DispatchSource.makeTimerSource(queue:.main)
    ///扫描持续时间
    var scanDuration = 10
    ///最大支持同时连接外设的数量
    var maxConnected = 1
    /// 按照厂商服务搜索
    var scanWithServices = [String](){
        didSet{
            let array = scanWithServices.compactMap{ element in
                return !services.contains{$0.uuidString == element} ? CBUUID(string: element) : nil
            }
            services += array
        }
    }
    /// 按设备名称搜索
    var scanPeripheralNames = [String]()
    ///扫描block
    private var updateScanBlock:scanResult?
    ///绑定/解绑 block
    var connectBlock:connectResult?
    /// 服务
    private var services = [CBUUID]()
    ///连接的外设有多少个services
    private var servicesCount = 0
    ///其他筛选条件
    var scanWithOther:scanFilter?
    
    lazy var central : CBCentralManager =
    {
        return CBCentralManager(delegate:self, queue: DispatchQueue.main)
    }()
    
    var status : BLEStatus = .normal{
        didSet{
            DispatchQueue.main.async {
                NotificationCenter.default.post(name:NSNotification.Name(BLEStatusUpdateNotifyName), object:nil)
            }
        }
    }
    
    /// 开始扫描
    func startScan(result:scanResult?, option:[String:Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:false,
                                                                   CBCentralManagerOptionShowPowerAlertKey:true]){
        updateScanBlock = result
        searchedPeripherals = connectedPeripherals
        
        if central.state == .poweredOn{
            /// 第一个参数：是否重复扫描已经发现的设备。如果需要不断获取最新信号强度RSSI，此参数要设为YES
            /// 第二个参数：蓝牙没打开，是否弹出提示框
            /* let option = [CBCentralManagerScanOptionAllowDuplicatesKey:false,
             CBCentralManagerOptionShowPowerAlertKey:true]*/
            central.scanForPeripherals(withServices:self.services, options:option)
        }
        
        ///x秒后停止扫描
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(scanDuration)) {[weak self] in
            self?.stopScan()
        }
    }
    
    /// 停止扫描
    func stopScan(){
        central.stopScan()
    }
    
    /// 连接指定的外围设备，通过MAC地址
    func connect(_ peripheral:CBPeripheral?=nil,_ mac:String?=nil, _ block:connectResult?=nil){
        guard let p = map(peripheral,mac), p.state != .connected, p.state != .connecting else{
            block?(status)
            return
        }
        connectBlock = block
        /// 第一个参数：在程序被挂起时，连接成功显示alert提醒框
        /// 第二个参数：在程序被挂起时，断开连接显示alert提醒框
        /// 第三个参数:   在程序被挂起时，显示所有的提醒消息
        let option = [CBConnectPeripheralOptionNotifyOnConnectionKey:true,
                   CBConnectPeripheralOptionNotifyOnDisconnectionKey:true,
                    CBConnectPeripheralOptionNotifyOnNotificationKey:true]
        central.connect(p, options:option)
    }
    
    /// 断开指定外围设备，通过MAC地址
    func disConnect(_ peripheral:CBPeripheral?=nil,_ mac:String?=nil, _ block:connectResult?=nil){
        guard let p = map(peripheral,mac) else{
            block?(status)
            return
        }
        
        connectBlock = block
        central.cancelPeripheralConnection(p)
        connectedPeripherals.removeAll{$0 == p}
        ///主动断开，连接记录也重新生成
        lastConnectedUUID = connectedPeripherals.map{String(format:"%@|",$0.identifier.uuidString)}.joined()
    }
    
    ///按照mac和peripheral 选出具体的设备
    private func map(_ peripheral:CBPeripheral?=nil,_ mac:String?=nil)->CBPeripheral?{
        if peripheral == nil && (mac == nil || mac == ""){
            return nil
        }else if mac != nil && mac != ""{
            return searchedPeripherals.filter{$0.mac == mac}.first
        }else {
            return  peripheral
        }
    }
    
    ///重连上次连接的
    func reConnect(){
        /// 1, 如果已连接的外设数组，有值，则先连接。
        let array = lastConnectedUUID.components(separatedBy: "|")
        for per in searchedPeripherals{
            if array.contains(per.identifier.uuidString){
                connect(per)
            }
        }
    }
    
    
    /// 获取系统底层连接的外围设备，系统底层连接时候，有可能app搜索不到该设备
    private func systemConnected(){
        let array = central.retrieveConnectedPeripherals(withServices:services)
        let unconnectArray = array.filter{$0.state == .disconnected || $0.mac.count <= 0}
        let temp = unconnectArray.filter{!searchedPeripherals.contains($0)}
        searchedPeripherals += temp
    }
    
    ///定时器方法
    private func timerAction(){
        timer.setEventHandler(handler:{[weak self] in
            self?.systemConnected()
            self?.reConnect()
        })
        timer.schedule(deadline: .now(), repeating: 1)
        timer.resume()
    }
}

//MARK: - CBCentralManagerDelegate
extension CCBleManager : CBCentralManagerDelegate
{
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        DispatchQueue.main.async { [self] in
            self.status = self.status == .DFU ? self.status : .normal
            self.searchedPeripherals.removeAll()
            switch central.state {
            case .unknown:
                break
            case .resetting:
                break
            case .unsupported:
                break
            case .unauthorized:
                break
            case .poweredOn:
                self.timerAction()
                self.startScan(result:self.updateScanBlock)
            case .poweredOff:
                self.timer.suspend()
                self.connectedPeripherals.removeAll()
                self.status = .connectedFail
            default:
                break
            }
        }
    }
    
    ///搜索到了设备
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        ///1.获取外围设备的mac
        let data = advertisementData["kCBAdvDataManufacturerData"] as? NSData ?? NSData()
        peripheral.advertData = data
        peripheral.mac = self.tranToMac(data)
        
        MyPrint("搜索到=======\(peripheral.name ?? "")")
        
        ///2.如果包含在已经搜索的列表里，则直接返回
        if self.searchedPeripherals.contains(peripheral){return}
        
        
        ///3.1 通过其他规则，过滤/筛选设备
        let fitler = self.scanWithOther?(peripheral) ?? true
        var array_names = fitler ? [peripheral] : [CBPeripheral]()
        ///3.2 通过名字，过滤/筛选设备
        let temp = self.scanPeripheralNames.compactMap{return ((peripheral.name ?? "").contains($0) && fitler) ? peripheral : nil}
        array_names = self.scanPeripheralNames.count > 0 ? temp : array_names
        self.searchedPeripherals += array_names
        
    }
    
    ///连接失败
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        DispatchQueue.main.async {
            MyPrint("-------------连接失败")
            self.status = .connectedFail
            self.disConnect(peripheral,nil,self.connectBlock)
        }
    }
    /// 断开连接
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        DispatchQueue.main.async {
            MyPrint("-------------断开链接")
            self.status = .disconnect
            self.connectedPeripherals.removeAll{$0 == peripheral}
        }
    }
    
    /// 连接成功
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        DispatchQueue.main.async {
            MyPrint("-------------连接成功 \(peripheral)  mac = \(peripheral.mac)")
            //central.stopScan()
            peripheral.delegate = self
            peripheral.discoverServices(nil)///查找所有服务
            ///同下面注释的一样
            ///self.status = self.status == .DFU ? self.status : .connectedSucces
            ///这里注销一下，等到所有服务和特征搜索完毕，再返回，否则在连接成功的回调里，立马发送协议，可能这时候服务特征都没搜索完毕，发送协议会不起作用
            ///self.connectBlock?(self.status)
        }
    }
}

//MARK: - CBPeripheralDelegate
extension CCBleManager:CBPeripheralDelegate
{
    ///搜索到服务
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if error != nil {
            MyPrint("\n\n=====didDiscoverServices错误：\(error)")
            return
        }
        servicesCount = 0
        for service in peripheral.services!{
            print("+++++++++++++++++ 服务 = \(service.uuid.uuidString)")
            servicesCount += 1
            peripheral.discoverCharacteristics(nil, for: service)///查找特征值
        }
    }
    
    ///搜索到特征值
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if error != nil {
            MyPrint("\n\n=====didDiscoverCharacteristicsFor错误：\(error)")
            return
        }
        var array = [[String:CBCharacteristic]]()
        for char in service.characteristics!{
            array.append([char.uuid.uuidString:char])
            peripheral.setNotifyValue(true, for: char)///订阅, 实时接收
        }
        peripheral.characteristics[service.uuid.uuidString] = array
        
        if servicesCount > 1 {
            servicesCount -= 1
            return
        }
        ///这个时候回调，再发协议，不会有任何错误了
        self.status = self.status == .DFU ? self.status : .connectedSucces
        if self.connectedPeripherals.contains(peripheral) == false && self.status != .DFU{
            ///如果已连接的外围设备，大于等于最大连接数量，则自动断开最早连接的外围设备
            if self.connectedPeripherals.count >= maxConnected{
                self.disConnect(self.connectedPeripherals.first!)
            }
            self.connectedPeripherals.append(peripheral)
            self.lastConnectedUUID = self.connectedPeripherals.map{String(format:"%@|",$0.identifier.uuidString)}.joined()
        }
        
    }
    
    
    /// 设置通知的回调
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            MyPrint("\n\n=====didUpdateNotificationStateFor错误：\(error)")
        }
    }
    
    ///写数据回调
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            MyPrint("\n\n=====didWriteValueFor错误：\(error)")
        }
    }
    ///数据更新
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            MyPrint("\n\n=====didUpdateValueFor错误：\(error)")
            return
        }
        BleDataTool.handleData(peripheral, didUpdateValueFor: characteristic, error: error)
    }
}


//MARK: - CBPeripheral扩展
private var macKey = "macKey"
private var advertDataKey = "advertDataKey"
private var characteristicsKey = "characteristicsKey"
extension CBPeripheral{
    @objc var mac : String{
        set {objc_setAssociatedObject(self, &macKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)}
        get{return objc_getAssociatedObject(self, &macKey) as? String ?? ""}
    }
    
    @objc var advertData : NSData{
        set {objc_setAssociatedObject(self, &advertDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
        get{return objc_getAssociatedObject(self, &advertDataKey) as? NSData ??  NSData()}
    }
    
    @objc var characteristics : [String : [[String:CBCharacteristic]]]{
        set {objc_setAssociatedObject(self, &characteristicsKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
        get{return objc_getAssociatedObject(self, &characteristicsKey) as? [String : [[String:CBCharacteristic]]] ??  [String : [[String:CBCharacteristic]]]()}
    }
}

extension CCBleManager{
    func tranToMac(_ data:NSData)->String{
        let bytes = [UInt8](data) //byte数组
        var str = ""
        for i in 0..<bytes.count {
            str += String(format:"%02lX:", bytes[i])
        }
        if str.count < 22 {return ""}
        let mac = str[6...22]
        return mac
    }
}

